
There are no definitive answers when it comes to detecting / extracting stops from movement trajectories. Due to tracking inaccuracies, movement speed rarely goes to true zero. GPS tracks, for example, tend to keep moving around the object's stop location.
Suitable stop definitions are also highly application dependent. For example, an application may be interested in analyzing trip purposes. To do so, analysts would be interested in stops that are longer than, for example, 5 minutes and may try to infer the purpose of the stop from the stop location and time. Shorter stops, such as delays at traffic lights, however would not be relevant for this appication.
In the MovingPandas TrajectoryStopDetector implementation, a stop is detected if the movement stays within an area of specified size for at least the specified duration.
import pandas as pd
import geopandas as gpd
import movingpandas as mpd
import shapely as shp
import hvplot.pandas
import matplotlib.pyplot as plt
from geopandas import GeoDataFrame, read_file
from shapely.geometry import Point, LineString, Polygon
from datetime import datetime, timedelta
from holoviews import opts
import warnings
warnings.filterwarnings('ignore')
plot_defaults = {'linewidth':5, 'capstyle':'round', 'figsize':(9,3), 'legend':True}
opts.defaults(opts.Overlay(active_tools=['wheel_zoom'], frame_width=500, frame_height=400))
mpd.show_versions()
MovingPandas 0.11.rc1 SYSTEM INFO ----------- python : 3.9.13 | packaged by conda-forge | (main, May 27 2022, 16:50:36) [MSC v.1929 64 bit (AMD64)] executable : H:\miniconda3\envs\movingpandas\python.exe machine : Windows-10-10.0.19043-SP0 GEOS, GDAL, PROJ INFO --------------------- GEOS : None GEOS lib : None GDAL : 3.5.0 GDAL data dir: None PROJ : 9.0.0 PROJ data dir: H:\miniconda3\pkgs\proj-9.0.0-h1cfcee9_1\Library\share\proj PYTHON DEPENDENCIES ------------------- geopandas : 0.11.1 pandas : 1.4.3 fiona : 1.8.21 numpy : 1.23.1 shapely : 1.8.2 rtree : 1.0.0 pyproj : 3.3.0 matplotlib : 3.5.2 mapclassify: 2.4.3 geopy : 2.2.0 holoviews : 1.14.8 hvplot : 0.8.0 geoviews : 1.9.5 stonesoup : 0.1b9
gdf = read_file('../data/geolife_small.gpkg')
traj_collection = mpd.TrajectoryCollection(gdf, 'trajectory_id', t='t')
my_traj = traj_collection.trajectories[0]
my_traj
Trajectory 1 (2008-12-11 04:42:14 to 2008-12-11 05:15:46) | Size: 466 | Length: 6207.0m Bounds: (116.385602, 39.862378, 116.393553, 39.898723) LINESTRING (116.391305 39.898573, 116.391317 39.898617, 116.390928 39.898613, 116.390833 39.898635,
traj_plot = my_traj.hvplot(title='Trajectory {}'.format(my_traj.id), line_width=7.0, tiles='CartoLight', color='slategray')
traj_plot
detector = mpd.TrajectoryStopDetector(my_traj)
%%time
stop_time_ranges = detector.get_stop_time_ranges(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 703 ms Wall time: 857 ms
for x in stop_time_ranges:
print(x)
Traj 1: 2008-12-11 04:42:14 - 2008-12-11 04:43:32 (duration: 0 days 00:01:18) Traj 1: 2008-12-11 04:43:50 - 2008-12-11 04:47:48 (duration: 0 days 00:03:58) Traj 1: 2008-12-11 04:50:06 - 2008-12-11 04:51:24 (duration: 0 days 00:01:18) Traj 1: 2008-12-11 04:54:50 - 2008-12-11 04:55:54 (duration: 0 days 00:01:04) Traj 1: 2008-12-11 05:02:03 - 2008-12-11 05:06:40 (duration: 0 days 00:04:37) Traj 1: 2008-12-11 05:07:19 - 2008-12-11 05:08:31 (duration: 0 days 00:01:12) Traj 1: 2008-12-11 05:11:17 - 2008-12-11 05:14:43 (duration: 0 days 00:03:26)
%%time
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 719 ms Wall time: 735 ms
stop_points
| geometry | start_time | end_time | traj_id | duration_s | |
|---|---|---|---|---|---|
| stop_id | |||||
| 1_2008-12-11 04:42:14 | POINT (116.39112 39.89861) | 2008-12-11 04:42:14 | 2008-12-11 04:43:32 | 1 | 78.0 |
| 1_2008-12-11 04:43:50 | POINT (116.39060 39.89834) | 2008-12-11 04:43:50 | 2008-12-11 04:47:48 | 1 | 238.0 |
| 1_2008-12-11 04:50:06 | POINT (116.38936 39.88924) | 2008-12-11 04:50:06 | 2008-12-11 04:51:24 | 1 | 78.0 |
| 1_2008-12-11 04:54:50 | POINT (116.39236 39.87851) | 2008-12-11 04:54:50 | 2008-12-11 04:55:54 | 1 | 64.0 |
| 1_2008-12-11 05:02:03 | POINT (116.39289 39.86386) | 2008-12-11 05:02:03 | 2008-12-11 05:06:40 | 1 | 277.0 |
| 1_2008-12-11 05:07:19 | POINT (116.39024 39.86383) | 2008-12-11 05:07:19 | 2008-12-11 05:08:31 | 1 | 72.0 |
| 1_2008-12-11 05:11:17 | POINT (116.38591 39.86519) | 2008-12-11 05:11:17 | 2008-12-11 05:14:43 | 1 | 206.0 |
stop_point_plot = traj_plot * stop_points.hvplot(geo=True, size='duration_s', color='deeppink')
stop_point_plot
%%time
stops = detector.get_stop_segments(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 688 ms Wall time: 700 ms
stops
TrajectoryCollection with 7 trajectories
stop_segment_plot = stop_point_plot * stops.hvplot(size=200, line_width=7.0, tiles=None, color='orange')
stop_segment_plot
%%time
split = mpd.StopSplitter(my_traj).split(min_duration=timedelta(seconds=60), max_diameter=100)
CPU times: total: 656 ms Wall time: 705 ms
split
TrajectoryCollection with 7 trajectories
split.to_traj_gdf()
| traj_id | start_t | end_t | geometry | length | direction | |
|---|---|---|---|---|---|---|
| 0 | 1_2008-12-11 04:43:32 | 2008-12-11 04:43:32 | 2008-12-11 04:43:50 | LINESTRING (116.39083 39.89863, 116.38941 39.8... | 251.838254 | 198.648318 |
| 1 | 1_2008-12-11 04:47:48 | 2008-12-11 04:47:48 | 2008-12-11 04:50:06 | LINESTRING (116.38984 39.89824, 116.38974 39.8... | 1032.611896 | 184.305030 |
| 2 | 1_2008-12-11 04:51:24 | 2008-12-11 04:51:24 | 2008-12-11 04:54:50 | LINESTRING (116.39010 39.88925, 116.39027 39.8... | 1369.652684 | 169.759596 |
| 3 | 1_2008-12-11 04:55:54 | 2008-12-11 04:55:54 | 2008-12-11 05:02:03 | LINESTRING (116.39189 39.87821, 116.39183 39.8... | 1918.855972 | 176.302880 |
| 4 | 1_2008-12-11 05:06:40 | 2008-12-11 05:06:40 | 2008-12-11 05:07:19 | LINESTRING (116.39227 39.86397, 116.39217 39.8... | 124.917120 | 262.140310 |
| 5 | 1_2008-12-11 05:08:31 | 2008-12-11 05:08:31 | 2008-12-11 05:11:17 | LINESTRING (116.38974 39.86382, 116.38967 39.8... | 439.686682 | 300.319621 |
| 6 | 1_2008-12-11 05:14:43 | 2008-12-11 05:14:43 | 2008-12-11 05:15:46 | LINESTRING (116.38591 39.86545, 116.38603 39.8... | 84.267811 | 133.034126 |
stop_segment_plot + split.hvplot(title='Trajectory {} split at stops'.format(my_traj.id), line_width=7.0, tiles='CartoLight')
The process is the same as for individual trajectories.
%%time
detector = mpd.TrajectoryStopDetector(traj_collection)
stop_points = detector.get_stop_points(min_duration=timedelta(seconds=120), max_diameter=100)
len(stop_points)
CPU times: total: 9.12 s Wall time: 9.35 s
26
ax = traj_collection.plot(figsize=(7,7))
stop_points.plot(ax=ax, color='red')
<AxesSubplot:>